/*
 * mkboothead.c
 *
 * (C) Copyright 2007-2011
 * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
 * Tom Cubie <tangliang@allwinnertech.com>
 *
 * Copyright(c) 2021 Cai_XL <Cai_XL@outlook.com>
 * bilibili : https://space.bilibili.com/54910927
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <stdint.h>
#include <string.h>

// do we need to swap bytes in endianess opration
//#define ENDIANESS_SWAP

// a SPL will be loaded to BROM range from 0x0000 0000 to 0x0000 8000,32KiB in total
#define SPL_SIZE        (0x8000)            // size of BROM
#define BIN_CODE_SIZE   (SPL_SIZE-0x60)     // a boothead uses 0x60 (96) Bytes

// from {u-boot}/tools/mksunxiboot.c and {u-boot}/arch/arm/include/asm/arch-sunxi/spl.h
#define MAGIC_STR       "eGON.BT0"
#define SPL_SIGNATURE   "SPL"
#define STAMP_VALUE     0x5F0A6C39

// a boothead consists of:
typedef struct
{
    uint32_t JumpInstruction;   // jump instruction
    uint8_t  Magic[8];	        /* ="eGON.BT0" or "eGON.BT1", not C-style str */
	uint32_t CheckSum;	        /* generated by PC */
	uint32_t Length;	        /* generated by PC */
	uint8_t  SPL_Signature[4];  // ="SPL2"
	uint32_t reserved1[18];     // igonre them
}Boothead_TypeDef;

// a bootable image consists of:
typedef struct
{
    Boothead_TypeDef boothead;      // a boothead
    uint8_t bincode[BIN_CODE_SIZE]; // executable code
}SPL_Img_TypeDef;


// CPU-Endian to Little-Endian
static uint32_t __cpu_to_le32(uint32_t val)
{
    #ifdef ENDIANESS_SWAP
    uint8_t *p = (uint8_t*)&val;
    uint8_t tmp;

    // swap bytes
    tmp = p[0];
    p[0] = p[3];
    p[3] = tmp;

    tmp = p[1];
    p[1] = p[2];
    p[2] = tmp;
    #endif

    return val;
}

// Little-Endian to CPU-Endian
static uint32_t __le32_to_cpu(uint32_t val)
{
    // they use same algorithm
    return __cpu_to_le32(val);
}

int main(int argc,char *argv[])
{
    // we need a fileame
    if(argc != 2)
    {
        printf("\nusage:mkboothead <file name> \n");
        printf("    <file name>   : a binary file without valid boothead\n\n");
        return -1;
    }

    // then open it
    FILE *fp = fopen(argv[1],"rb");             // binary & read only
    if(fp == NULL)
    {
        printf("cann't open file :%s\n",argv[1]);
        return -1;
    }
    // get file size
    fseek(fp,0,SEEK_END);                       // set index to the end
    uint32_t file_size = ftell(fp);             // the index is file size now,read it
    if(file_size > SPL_SIZE)                    // can it fit BROM ?
    {
        printf("file %s is not within BROM\n",argv[1]);
        fclose(fp);
        return -1;
    } 
    fseek(fp,0,SEEK_SET);                       // reset index to the head

    // read the file
    SPL_Img_TypeDef SPL_Img;                    // now we can create a bootable image
    memset(&SPL_Img,0,sizeof(SPL_Img));         // clear it
    uint32_t count;
    count = fread(&SPL_Img,1,file_size,fp);     // read all of it
    if(count != file_size)
    {
        printf("read file %s error\n",argv[1]);
        fclose(fp);
        return -1;
    }
    fclose(fp);

    // calculate the CheckSum of image

    SPL_Img.boothead.Length             = __cpu_to_le32(0x00008000);    // fix length to 32KiB
    SPL_Img.boothead.CheckSum           = __cpu_to_le32(STAMP_VALUE);   // fill CkeckSum with stamp value

    uint32_t sum = 0, i;
    uint32_t *p = (uint32_t*)&SPL_Img;
    for(i = 0;i < 0x2000;i++)
    {
        sum += __le32_to_cpu(p[i]);
    }
    SPL_Img.boothead.CheckSum           = __cpu_to_le32(sum);           // now we have CheckSum

    // write it back
    fp = fopen(argv[1],"wb");
    count = fwrite(&SPL_Img,1,0x8000,fp);
    if(count != 0x8000)
    {
        printf("write file %s error\n",argv[1]);
        fclose(fp);
        return -1;
    }
    fclose(fp);
    printf("boothead has been fixed to %s\n",argv[1]);

    // all done
    return 0;
}